/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <vfs_littlefs.h>
/* includes (standard library, system) ---------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <vfs.h>
#include <x_errno.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static int littlefs_errno(int res)
{
    int err = 0;
    switch (res) {
    case LFS_ERR_OK:
        return 0;
    case LFS_ERR_IO:
        err = EIO;
        break;
    case LFS_ERR_NOENT:
        err = ENOENT;
        break;
    case LFS_ERR_EXIST:
        err = EEXIST;
        break;
    case LFS_ERR_NOTDIR:
        err = ENOTDIR;
        break;
    case LFS_ERR_ISDIR:
        err = EISDIR;
        break;
    case LFS_ERR_NOTEMPTY:
        err = ENOTEMPTY;
        break;
    case LFS_ERR_BADF:
        err = EBADF;
        break;
    case LFS_ERR_INVAL:
        err = EINVAL;
        break;
    case LFS_ERR_NOSPC:
        err = ENOSPC;
        break;
    case LFS_ERR_NOMEM:
        err = ENOMEM;
        break;
    case LFS_ERR_CORRUPT:
        err = ENOEXEC;
        break;
    default:
        err = EIO;
        break;
    }

    return -err;
}

static int littlefs_flags_get(int oflags)
{
    int flags = 0;

    switch (oflags & O_ACCMODE) {
    case O_RDONLY:
        flags |= LFS_O_RDONLY;
        break;
    case O_WRONLY:
        flags |= LFS_O_WRONLY;
        break;
    case O_RDWR:
        flags |= LFS_O_RDWR;
        break;
    default:
        break;
    }

    if (oflags & O_CREAT) {
        flags |= LFS_O_CREAT;
    }

    if ((oflags & O_CREAT) && (oflags & O_EXCL)) {
        flags |= LFS_O_CREAT;
    }

    if (oflags & O_TRUNC) {
        flags |= LFS_O_TRUNC;
    }

    if (oflags & O_APPEND) {
        flags |= LFS_O_APPEND;
    }

    return flags;
}

static int littlefs_op_open(struct file *fp, const char *path, int flags, int mode)
{
    lfs_t *fs = (lfs_t *)fp->fl_mp->mp_data;
    lfs_file_t *f = (lfs_file_t *)malloc(sizeof(lfs_file_t));
    if (f == NULL) {
        return -ENOMEM;
    }
    memset(f, 0, sizeof(lfs_file_t));
    int ret = lfs_file_open(fs, f, path, littlefs_flags_get(flags));
    if (ret == LFS_ERR_OK) {
        fp->fl_data = (uintptr_t)f;
    } else {
        free(f);
    }
    return littlefs_errno(ret);
}

static int littlefs_op_close(struct file *fp)
{
    lfs_t *fs = (lfs_t *)fp->fl_mp->mp_data;
    lfs_file_t *f = (lfs_file_t *)fp->fl_data;
    if (f == NULL) {
        return -EBADF;
    }
    int ret = lfs_file_close(fs, f);
    if (ret != LFS_ERR_OK) {
        return littlefs_errno(ret);
    }
    free(f);
    fp->fl_data = 0;
    return 0;
}

static int littlefs_op_read(struct file *fp, char *buf, size_t nbyte)
{
    lfs_t *fs = (lfs_t *)fp->fl_mp->mp_data;
    lfs_file_t *f = (lfs_file_t *)fp->fl_data;
    if (f == NULL) {
        return -EBADF;
    }
    lfs_ssize_t ret = lfs_file_read(fs, f, buf, (lfs_size_t)nbyte);
    if (ret < 0) {
        return littlefs_errno((int)ret);
    }
    return (int)ret;
}

static int littlefs_op_write(struct file *fp, const char *buf, size_t nbyte)
{
    lfs_t *fs = (lfs_t *)fp->fl_mp->mp_data;
    lfs_file_t *f = (lfs_file_t *)fp->fl_data;
    if (f == NULL) {
        return -EBADF;
    }
    lfs_ssize_t ret = lfs_file_write(fs, f, buf, (lfs_size_t)nbyte);
    if (ret < 0) {
        return littlefs_errno((int)ret);
    }
    return (int)ret;
}

static int littlefs_op_lseek(struct file *fp, int offset, int whence)
{
    lfs_t *fs = (lfs_t *)fp->fl_mp->mp_data;
    lfs_file_t *f = (lfs_file_t *)fp->fl_data;
    if (f == NULL) {
        return -EBADF;
    }
    lfs_soff_t ret = lfs_file_seek(fs, f, (lfs_soff_t)offset, whence);
    if (ret < 0) {
        return littlefs_errno((int)ret);
    }
    return (int)ret;
}

static int littlefs_op_ioctl(struct file *fp, int request, va_list valist)
{
    return -ENOSYS;
}

static int littlefs_op_sync(struct file *fp)
{
    lfs_t *fs = (lfs_t *)fp->fl_mp->mp_data;
    lfs_file_t *f = (lfs_file_t *)fp->fl_data;
    if (f == NULL) {
        return -EBADF;
    }
    int ret = lfs_file_sync(fs, f);
    return littlefs_errno(ret);
}

static int littlefs_op_stat(struct mount_point *mp, const char *path, struct stat *st)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    struct lfs_info info = {0};
    int ret = lfs_stat(fs, path, &info);
    if (ret == LFS_ERR_OK) {
        st->st_size = info.size;
        st->st_mode = ((info.type == LFS_TYPE_DIR) ? S_IFDIR : S_IFREG);
    }
    return littlefs_errno(ret);
}

static int littlefs_op_unlink(struct mount_point *mp, const char *path)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    int ret = lfs_remove(fs, path);
    return littlefs_errno(ret);
}

static int littlefs_op_rename(struct mount_point *mp, const char *oldname, const char *newname)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    int ret = lfs_rename(fs, oldname, newname);
    return littlefs_errno(ret);
}

static int littlefs_op_opendir(struct mount_point *mp, struct dir *dp, const char *path)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    lfs_dir_t *d = (lfs_dir_t *)malloc(sizeof(lfs_dir_t));
    if (d == NULL) {
        return -ENOMEM;
    }
    memset(d, 0, sizeof(lfs_dir_t));
    int ret = lfs_dir_open(fs, d, path);
    if (ret != LFS_ERR_OK) {
        free(d);
        return littlefs_errno(ret);
    }
    dp->dr_data = (uintptr_t)d;
    return LFS_ERR_OK;
}

static int littlefs_op_readdir(struct mount_point *mp, struct dir *dp, struct dirent *de)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    lfs_dir_t *d = (lfs_dir_t *)dp->dr_data;
    if (d == NULL) {
        return -EBADF;
    }
    struct lfs_info info = {0};
    int ret;
    do {
        ret = lfs_dir_read(fs, d, &info);
    } while ((ret >= 0) && ((strcmp(info.name, ".") == 0 || strcmp(info.name, "..") == 0)));
    if (ret < 0) {
        return -ENOENT;
    }
    int len = min(sizeof(info.name), sizeof(de->d_name)) - 1;
    strncpy((char *)de->d_name, (const char *)info.name, len);
    de->d_name[len] = '\0';
#ifndef CONFIG_OS_LINUX
    de->d_size = info.size;
#endif

    if (info.type == LFS_TYPE_DIR) {
        de->d_type = DT_DIR;
    } else {
        de->d_type = DT_REG;
    }
    return 0;
}

static int littlefs_op_closedir(struct mount_point *mp, struct dir *dp)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    lfs_dir_t *d = (lfs_dir_t *)dp->dr_data;
    if (d == NULL) {
        return -EBADF;
    }
    int ret = lfs_dir_close(fs, d);
    if (ret != LFS_ERR_OK) {
        return littlefs_errno(ret);
    }
    free(d);
    dp->dr_data = 0;
    return 0;
}

static int littlefs_op_mkdir(struct mount_point *mp, const char *path, int mode)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    int ret = lfs_mkdir(fs, path);
    return littlefs_errno(ret);
}

static int littlefs_op_rmdir(struct mount_point *mp, const char *path)
{
    lfs_t *fs = (lfs_t *)mp->mp_data;
    int ret = lfs_remove(fs, path);
    return littlefs_errno(ret);
}

static int littlefs_op_mount(struct mount_point *mp, va_list valist)
{
    struct lfs_config *config = va_arg(valist, struct lfs_config *);
    if (config == NULL) {
        return -EINVAL;
    }
    lfs_t *fs = (lfs_t *)malloc(sizeof(lfs_t));
    if (fs == NULL) {
        return -ENOMEM;
    }
    memset(fs, 0, sizeof(lfs_t));
    int ret = lfs_mount(fs, config);
    if (ret == LFS_ERR_CORRUPT) {
        lfs_format(fs, config);
        ret = lfs_mount(fs, config);
    }
    if (ret != LFS_ERR_OK) {
        goto EXIT_FREE_FS;
    }
    mp->mp_data = (uintptr_t)fs;

    return 0;

EXIT_FREE_FS:
    free(fs);
    return ret;
}

static int littlefs_op_unmount(struct mount_point *mp, va_list valist)
{
    int ret = 0;
    lfs_t *fs = (lfs_t *)mp->mp_data;

    if (fs != NULL) {
        if ((ret = lfs_unmount(fs)) != 0) {
            return ret;
        }
        free(fs);
        mp->mp_data = 0;
    }
    return ret;
}

static const struct f_ops littlefs_fops = {
    .open = littlefs_op_open,
    .close = littlefs_op_close,
    .read = littlefs_op_read,
    .write = littlefs_op_write,
    .lseek = littlefs_op_lseek,
    .ioctl = littlefs_op_ioctl,
    .sync = littlefs_op_sync,
};

static const struct m_ops littlefs_mops = {
    .stat = littlefs_op_stat,
    .unlink = littlefs_op_unlink,
    .rename = littlefs_op_rename,
    .mkdir = littlefs_op_mkdir,
    .rmdir = littlefs_op_rmdir,
    .opendir = littlefs_op_opendir,
    .readdir = littlefs_op_readdir,
    .closedir = littlefs_op_closedir,
    .mount = littlefs_op_mount,
    .unmount = littlefs_op_unmount,
};

static struct file_system littlefs_fs = {
    "lfs",
    &littlefs_fops,
    &littlefs_mops,
};

int littlefs_init(void)
{
    if (vfs_fs_register(&littlefs_fs) != 0) {
        return -1;
    }

    return 0;
}

int littlefs_exit(void)
{
    if (vfs_fs_unregister(&littlefs_fs) != 0) {
        return -1;
    }

    return 0;
}

int littlefs_mount(const char *path, const struct lfs_config *config)
{
    return vfs_mount("lfs", path, config);
}

int littlefs_unmount(const char *path)
{
    return vfs_unmount(path);
}
